/*
 ----------------------------------------------------------------------------------
                           "THE BEER-WARE COPYRIGHT LICENSE"
                                     (Revision 43)
                                      -----------
    Rymek [rymek@rymek.org] wrote this file. As long as You retain this notice and
    this file has been sent directly to You by the author, You can do whatever You
    want with this stuff as long as You don't sell or rent it. If we meet some day,
    and You think this stuff is worth it, You can buy me a beer in return :-)
                                                                 __________
                                                                 Paweł Rymaszewski
  ----------------------------------------------------------------------------------
 */

package processing.logic.threads;

import constants.Constants;
import constants.MessageConstants;
import constants.UiConstants;
import org.dom4j.DocumentException;
import processing.logic.MessageParser;
import processing.logic.util.Connector;
import processing.logic.util.Decoder;
import processing.logic.util.Mapper;
import processing.objects.Result;
import processing.objects.Results;
import processing.objects.SearchParameters;
import processing.objects.Server;
import ui.messages.MessageBoxes;

import java.io.File;
import java.io.IOException;
import java.sql.SQLException;
import java.util.zip.DataFormatException;

import static utils.FileUtils.exportContent;

/**
 * Class handles fetching of single message instance
 *
 * @author rymek
 *         User: rymek
 *         Date: 15.07.11
 *         Time: 09:48
 */
@SuppressWarnings({"NullableProblems", "ResultOfMethodCallIgnored"})
public class FetcherThread extends Connector implements Runnable {

    private final Result unparsedResult;              // unparsed result to fetch
    private final Results parsedResults;              // parsed results to update
    private volatile Result parsedRequest;            // parsed request
    private volatile Result parsedResponse;           // parsed response
    private final SearchParameters searchParameters;  // search parameters
    private int retryOnErrorCount = 0;                // error count for DB connections

    /**
     * Constructor.
     * Also creates connection.
     *
     * @param unparsedResult unparsed result to fetch and parse
     * @param parsedResults  result object containing parsed results
     * @param server         server information
     */
    public FetcherThread(Result unparsedResult, Results parsedResults, Server server) {

        super(server);
        this.unparsedResult = unparsedResult;
        this.parsedResults = parsedResults;
        this.searchParameters = parsedResults.getSearchParameters();
    }

    @Override
    /**
     * Handles message processing:
     * - fetching from database
     * - matching regexp
     * - saving to file
     */
    public void run() {

        try {

            super.createConnection();

            MessageParser messageParser = new MessageParser(this.parsedResults.getSearchParameters());

            super.preparedStatement = Mapper.buildPreparedFetchQuery(this.parsedResults.getSearchParameters(), this.unparsedResult.getId(), this.connection);
            super.resultSet = preparedStatement.executeQuery();

            while (!this.connection.isClosed() && super.resultSet.next()) {

                Result parsedResult = new Result(this.unparsedResult);

                switch (parsedResult.getMessageInfo().getMessageType()) {

                    case MessageConstants.MSG_SYNC_MESSAGE: {

                        // different way of parsing custom publisher messages
                        if (parsedResult.getMessageInfo().isCustomPublisher())
                            parsedResult.setXmlString(new Decoder().formatMessage(resultSet.getString(1)));
                        else {

                            // this has to be read first since oracle Result Sets are sensitive:
                            // when data is extracted from a row in the resultset, the stream associated with a blob
                            // type is closed as soon as a column is read which has a greater index than the stream's column
                            parsedResult.setXmlString(new Decoder().Decode(super.resultSet.getBytes(2), super.resultSet.getInt(1)));

                            // determine if synchronous message beeing parsed is request or response
                            parsedResult.setLogtype(super.resultSet.getInt(3));

                            switch (parsedResult.getLogtype()) {

                                case MessageConstants.MSG_REQUEST: {
                                    this.parsedRequest = parsedResult;
                                    break;
                                }

                                case MessageConstants.MSG_RESPONSE: {
                                    this.parsedResponse = parsedResult;
                                    break;
                                }
                            }
                        }
                        break;
                    }

                    case MessageConstants.MSG_ASYNC_MESSAGE: {

                        // different way of parsing custom publisher messages
                        if (parsedResult.getMessageInfo().isCustomPublisher())
                            parsedResult.setXmlString(new Decoder().formatMessage(resultSet.getString(1)));
                        else
                            parsedResult.setXmlString(new Decoder().Decode(super.resultSet.getBytes(2), super.resultSet.getInt(1)));

                        this.parsedRequest = parsedResult;
                        break;
                    }
                }
            }

            // this has to be done after message fetching
            // so SYNC messages are guaranteed to have both
            // request and response to parse and export correctly
            switch (this.searchParameters.getMessageInfo().getMessageType()) {
                case MessageConstants.MSG_SYNC_MESSAGE: {

                    // perform checking for empty messages in case of timeout
                    // for sync message
                    this.checkForEmptyMessages();

                    // determine if message matches given models search criteria
                    if (messageParser.parseMessageBody(this.parsedRequest) || messageParser.parseMessageBody(this.parsedResponse)) {

                        this.exportResult(this.parsedRequest);
                        this.exportResult(this.parsedResponse);
                        this.parsedResults.addResults(this.parsedRequest, this.parsedResponse);

                    }
                    this.parsedRequest.setXmlString(null);
                    this.parsedResponse.setXmlString(null);
                    break;
                }

                case MessageConstants.MSG_ASYNC_MESSAGE: {

                    // determine if message matches given models search criteria
                    if (messageParser.parseMessageBody(this.parsedRequest)) {

                        this.exportResult(this.parsedRequest);
                        this.parsedResults.addRequestResult(this.parsedRequest);

                    }
                    this.parsedRequest.setXmlString(null);
                    break;
                }
            }

            // notify about task completion
            this.fireNotificationEvent();

            // closing the db connection and all open db objects
            super.cleanup();

        } catch (SQLException error) {

            if (this.retryOnErrorCount < Constants.DB_ACCESS_RETRIES) {

                this.retryOnErrorCount++;
                this.run();

            } else {

                this.fireNotificationEvent();
                MessageBoxes.sqlException(error);
            }

        } catch (DataFormatException | DocumentException error) {
            this.fireNotificationEvent();
            MessageBoxes.genericErrorMessage(error);

        } catch (IOException error) {
            this.parsedRequest.getXmlResultFile().delete();
            this.parsedResponse.getXmlResultFile().delete();
            this.fireNotificationEvent();

        }
    }

    /**
     * Checks if there is empty request or response
     * in sync message. If so, it generates default
     * message so algorithm performs parsing correclty
     */
    private void checkForEmptyMessages() {

        switch (this.searchParameters.getMessageInfo().getMessageType()) {
            case MessageConstants.MSG_SYNC_MESSAGE: {

                if (this.parsedRequest == null && this.parsedResponse == null) {
                    this.parsedRequest = Result.createEmptyRequest(this.unparsedResult);
                    this.parsedResponse = Result.createEmptyResponse(this.unparsedResult);
                }

                if (this.parsedRequest == null) {
                    this.parsedRequest = new Result(this.parsedResponse);
                    this.parsedRequest.setLogtype(1);
                    this.parsedRequest.setXmlString(UiConstants.EMPTY_MESSAGE_STRING);
                }

                if (this.parsedResponse == null) {
                    this.parsedResponse = new Result(this.parsedRequest);
                    this.parsedRequest.setLogtype(3);
                    this.parsedResponse.setXmlString(UiConstants.EMPTY_MESSAGE_STRING);
                }
                break;
            }

            case MessageConstants.MSG_ASYNC_MESSAGE: {
                if (this.parsedRequest == null) {
                    this.parsedRequest = new Result(this.unparsedResult);
                    this.parsedRequest.setXmlString(UiConstants.EMPTY_MESSAGE_STRING);
                }
                break;
            }
        }
    }


    /**
     * Exports Result to a file
     *
     * @param result - Result to export
     * @throws IOException - thrown when export fails
     */
    private void exportResult(Result result) throws IOException {

        result.setXmlResultFile(new File(Constants.TEMP_DIR + "\\" + this.parsedResults.getOutputDir() + "\\" + Mapper.generateMessageFileName(result, super.server)));
        result.applyHeader(Mapper.generateMessageHeader(result, super.server));

        // export contents to specific file
        exportContent(this.parsedResults.getOutputDir(), result.getXmlResultFile().getName(), result.getXmlString());
    }

    /**
     * Fires up notification event about thread finish.
     */
    private void fireNotificationEvent() {
        super.setChanged();
        super.notifyObservers();
    }
}
